﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;

using MandelbrotLib;

namespace Mandelbrot
{
    public partial class MandelbrotForm : Form
    {
        private enum CursorState
        {
            Idle,
            Tracking,
        }

        private struct CalcParamRecord
        {
            public RealNumber X0;
            public RealNumber Y0;
            public RealNumber X1;
            public RealNumber Y1;
            public int NumIterations;

            public CalcParamRecord(RealNumber x0, RealNumber y0,
                RealNumber x1, RealNumber y1, int numIterations)
            {
                X0 = x0.Copy();
                Y0 = y0.Copy();
                X1 = x1.Copy();
                Y1 = y1.Copy();
                NumIterations = numIterations;
            }
        }

        private delegate void ComputeEndDelegate();

        private CursorState mCursorState = CursorState.Idle;
        private Point mMDPos;
        private Point mOldPos;

        private Rectangle mOldRect;
        private Rectangle mNewRect;

        private RealNumber mX0_pre;
        private RealNumber mY0_pre;
        private RealNumber mX1_pre;
        private RealNumber mY1_pre;

        private RealNumber mX0;
        private RealNumber mY0;
        private RealNumber mX1;
        private RealNumber mY1;
        private int mNumIterations = 50;
        private MandelbrotSet mMandelbrot = new MandelbrotSet();
        private bool mComputing = false;

        Stack<CalcParamRecord> mCalcHistory = new Stack<CalcParamRecord>();

        private Bitmap[] mImageBuffer = new Bitmap[2];
        private int mImageBufferIndex = 0;

        private bool mHandleResize = false;

        public MandelbrotForm()
        {
            InitializeComponent();
        }

        private void pbxMandelbrot_MouseDown(object sender, MouseEventArgs e)
        {
            if (mComputing)
            {
                return;
            }
            lock (this)
            {
                if (mCursorState != CursorState.Idle)
                {
                    return;
                }
                if (e.Button == MouseButtons.Right)
                {
                    return;
                }

                mMDPos = new Point(e.X, e.Y);
                mOldPos = mMDPos;
                mCursorState = CursorState.Tracking;
            }
        }

        private void pbxMandelbrot_MouseUp(object sender, MouseEventArgs e)
        {
            if (mComputing)
            {
                return;
            }
            lock (this)
            {
                if (mCursorState != CursorState.Tracking)
                {
                    return;
                }
                if (e.Button == MouseButtons.Right)
                {
                    return;
                }

                CalcParamRecord old = new CalcParamRecord(
                    mX0, mY0, mX1, mY1, mNumIterations
                    );
                mCalcHistory.Push(old);

                EasySettingsForm dlg = new EasySettingsForm();
                dlg.NumOfIterations = this.mNumIterations;
                if (dlg.ShowDialog() == DialogResult.OK)
                {
                    this.mNumIterations = dlg.NumOfIterations;
                }

                mCursorState = CursorState.Idle;
                pbxMandelbrot.Invalidate();

                int w = pbxMandelbrot.Width;
                int h = pbxMandelbrot.Height;

                RealNumber rx = mX1 - mX0;
                RealNumber ry = mY1 - mY0;

                mX0_pre = mX0 + rx * mNewRect.X / w;
                mX1_pre = mX0 + rx * mNewRect.Right / w;
                mY0_pre = mY0 + ry * (h - mNewRect.Bottom) / h;
                mY1_pre = mY0 + ry * (h - mNewRect.Y) / h;

                if (CalculateRegion())
                {
                    Redraw();
                }
            }
        }

        private void pbxMandelbrot_MouseMove(object sender, MouseEventArgs e)
        {
            if (mCursorState == CursorState.Tracking)
            {
                int x00 = Math.Min(mMDPos.X, mOldPos.X);
                int y00 = Math.Min(mMDPos.Y, mOldPos.Y);
                int x01 = Math.Max(mMDPos.X, mOldPos.X);
                int y01 = Math.Max(mMDPos.Y, mOldPos.Y);
                mOldRect = new Rectangle(x00, y00, x01 - x00, y01 - y00);


                int x10 = Math.Min(mMDPos.X, e.X);
                int y10 = Math.Min(mMDPos.Y, e.Y);
                int x11 = Math.Max(mMDPos.X, e.X);
                int y11 = Math.Max(mMDPos.Y, e.Y);
                Pen pen = new Pen(new SolidBrush(Color.Black));
                mNewRect = new Rectangle(x10, y10, x11 - x10, y11 - y10);


                Rectangle invlRect = Rectangle.Union(mOldRect, mNewRect);
                invlRect.Width++;
                invlRect.Height++;

                mOldPos = new Point(e.X, e.Y);

                // repaint
                pbxMandelbrot.Invalidate(invlRect);
            }
        }

        private void pbxMandelbrot_Resize(object sender, EventArgs e)
        {
            if (mHandleResize)
            {
                HandleResize();
            }
        }

        private void HandleResize()
        {
            CreateImageBuffer();
            if (CalculateRegion())
            {
                Redraw();
            }
        }

        private void SwapBuffer()
        {
            mImageBufferIndex = 1 - mImageBufferIndex;
            pbxMandelbrot.Invalidate();
        }

        private bool CalculateRegion()
        {
            int w = pbxMandelbrot.Width;
            int h = pbxMandelbrot.Height;

            if (w == 0 || h == 0)
            {
                return false;
            }

            RealNumber rx = mX1_pre - mX0_pre;
            RealNumber ry = mY1_pre - mY0_pre;

            RealNumber gx = rx / w;
            RealNumber gy = ry / h;

            if (gx < gy)
            {   // expand x according to y
                RealNumber newRx = (ry * w) / h;
                RealNumber mod = (newRx - rx) / 2;

                mX0_pre = mX0_pre - mod;
                mX1_pre = mX1_pre + mod;
            }
            else if (gx > gy)
            {   // expand y according to x
                RealNumber newRy = (rx * h) / w;
                RealNumber mod = (newRy - ry) / 2;

                mY0_pre = mY0_pre - mod;
                mY1_pre = mY1_pre + mod;
            }

            return true;
        }

        private void ComputeEnd()
        {
            int w = pbxMandelbrot.Width;
            int h = pbxMandelbrot.Height;

            Bitmap image = mImageBuffer[1 - mImageBufferIndex];

            if (w != image.Width || h != image.Height)
            {
                return;
            }

            for (int i = 0; i < w; i++)
            {
                for (int j = 0; j < h; j++)
                {
                    int v = mMandelbrot[i, j];
                    v = v * 255 / mNumIterations;
                    Color color = Color.FromArgb(v, v, v);
                    image.SetPixel(i, h - j - 1, color);
                }
            }

            this.MainStatusStrip.Items[0].Text = "Iters: "
                + mNumIterations.ToString();
            this.MainStatusStrip.Items[1].Text = "Prec: "
                + mMandelbrot.Precision.ToString();
            this.MainStatusStrip.Items[2].Text =
                String.Format("({0:##.#####},{1:##.#####}) - ({2:##.#####},{3:##.#####})",
                this.mX0.ToDouble(), this.mY0.ToDouble(),
                this.mX1.ToDouble(), this.mY1.ToDouble());

            SwapBuffer();
        }

        private void Compute()
        {
            lock (this)
            {
                Size oldSize = this.pbxMandelbrot.Size;

                this.mComputing = true;
                this.pbxMandelbrot.UseWaitCursor = true;

                int w = pbxMandelbrot.Width;
                int h = pbxMandelbrot.Height;

                mX0 = mX0_pre;
                mY0 = mY0_pre;
                mX1 = mX1_pre;
                mY1 = mY1_pre;

                mX0.RoundTo(1);
                mY0.RoundTo(1);
                mX1.RoundTo(1);
                mY1.RoundTo(1);

                mMandelbrot.SetRegion(mX0, mY0, mX1, mY1, w, h);
                mMandelbrot.SetNumberOfIterations(mNumIterations);
                mMandelbrot.Calculate();

                this.pbxMandelbrot.UseWaitCursor = false;
                this.mComputing = false;
            }

            this.Invoke(new ComputeEndDelegate(this.ComputeEnd));
        }

        private void Redraw()
        {
            Thread thread = new Thread(new ThreadStart(Compute));
            thread.Start();
        }

        private void pbxMandelbrot_Paint(object sender, PaintEventArgs e)
        {
            e.Graphics.DrawImage(mImageBuffer[mImageBufferIndex], 
                new Point(0, 0));

            if (mCursorState == CursorState.Tracking)
            {
                using (Pen eraser = new Pen(new SolidBrush(Color.Transparent)))
                using (Pen pen = new Pen(new SolidBrush(Color.Red)))
                {
                    e.Graphics.DrawRectangle(eraser, mOldRect);
                    e.Graphics.DrawRectangle(pen, mNewRect);
                }
            }
        }

        private void CreateImageBuffer()
        {
            if (pbxMandelbrot.Width == 0 || pbxMandelbrot.Height == 0)
            {
                return;
            }
            for (int i = 0; i < mImageBuffer.Length; i++)
            {
                mImageBuffer[i] = new Bitmap(pbxMandelbrot.Width, pbxMandelbrot.Height);
            }
            mImageBufferIndex = 0;
        }

        private void MandelbrotForm_Load(object sender, EventArgs e)
        {
            mHandleResize = false;

            InitializeParameter();

            InitializeLayout();

            CreateImageBuffer();

            UpdateLayout();

            mHandleResize = true;

            HandleResize();
        }

        private void InitializeParameter()
        {
            mNumIterations = 100;

            mX0_pre = RealNumber.CreateByNumerator(-5).Mul(RealNumber.CreateByDenominator(2));
            mX1_pre = RealNumber.CreateByNumerator(3).Mul(RealNumber.CreateByDenominator(2));

            mY0_pre = RealNumber.CreateByNumerator(-2);
            mY1_pre = RealNumber.CreateByNumerator(2);
        }

        private void exitToolStripMenuItem_Click(object sender, EventArgs e)
        {
            this.Close();
        }

        private void pbxMandelbrot_Click(object sender, EventArgs e)
        {

        }

        private void optionToolStripMenuItem_Click(object sender, EventArgs e)
        {

        }

        private void InitializeLayout()
        {
        }

        private void UpdateLayout()
        {
            pbxMandelbrot.Height = this.ClientSize.Height 
                - this.MainStatusStrip.Height - this.MainMenu.Height;
        }

        private void MandelbrotForm_Resize(object sender, EventArgs e)
        {
            UpdateLayout();
        }

        private void pbxMandelbrot_MouseClick(object sender, MouseEventArgs e)
        {
            if (mComputing)
            {
                return;
            }

            lock (this)
            {
                if (e.Button == MouseButtons.Right)
                {
                    if (mCalcHistory.Count > 0)
                    {
                        CalcParamRecord record = mCalcHistory.Pop();
                        mX0_pre = record.X0;
                        mY0_pre = record.Y0;
                        mX1_pre = record.X1;
                        mY1_pre = record.Y1;
                        mNumIterations = record.NumIterations;

                        if (CalculateRegion())
                        {
                            Redraw();
                        }
                    }
                }
            }
        }

        private void aboutToolStripMenuItem_Click(object sender, EventArgs e)
        {
            MainAboutBox aboutbox = new MainAboutBox();
            aboutbox.ShowDialog();
        }
    }
}
